---
title: Custom Stencil
sidebar_position: 1
---

import { CustomStencil } from '../../src/components/tutorials/CustomStencil/CustomStencil';

## Idea

This guide describes the process of creating an one of possible stencils and you can apply this ideas to implement arbitrary stencil.

Let's consider the following stencil:

![Internal structure](/img/custom-stencil/idea.svg)

It doesn't look like the default stencils, at least because it has only one handler which is arranged not on the corner of a bounding box.

In addition, this stencil has the different resize logic. Unlike the default stencils it expands in all directions simultaneously.

![Internal structure](/img/custom-stencil/idea-resize.svg)

## Basic structure

A custom stencil is always a React component.

```tsx
import React from 'react';

export const CircleStencil = () => {
	return (
		<div className="circle-stencil"></div>
	)
}
```

Let's define basic requirements to a typical stencil:
- it should be inscribed to box is represented by coordinates (width, height, left, top)
- if stencil has aspect ratios it should provide `aspectRatio` property to inform the cropper resize algorithm about it (this method should return object with minimum and maximum aspect ratio values)
- it should call the corresponding cropper methods when user interact with the stencil (`moveCoordinates`, `moveCoordinatesEnd`, `resizeCoordinates`, `resizeCoordinatesEnd`, )
- it should display the cropped part of a image
- it should display the overlay around the stencil

## Stencil Props

First of all, it's need to describe service props that come from the cropper.

####  [`cropper`](/docs/concept/basic/#state)

It's the ref to [the cropper instance](/docs/components/Cropper).

#### [`image`](/docs/concept/basic/#image)

This prop is the object, that describes the properties of image and has following properties:

- `src` - the link to the image
- `width` - the image width
- `height` - the image height
- `transforms` - the transforms applied to the image
- `revoke` - flag that indicates should be the image revoked (by `window/revokeObjectURL`)
- `arrayBuffer` - the content of the image in bytes

## Stencil coordinates

The stencil coordinates can be easily calculated from `state`, so you should set the coordinates of your stencil **himself**.
It was done on purpose, to give you possibility to create the custom stencil more flexible.

That's how it can be done:

```tsx
import React from 'react';
import { StencilProps } from 'react-advanced-cropper';

export const CircleStencil = ({ cropper }: StencilProps) => {
	const coordinates = cropper.getStencilCoordinates();
	return (
		<div className="circle-stencil"></div>
	)
}
```

## Transitions

Transitions can be received in the similar way:

```tsx
import React from 'react';
import { StencilProps } from 'react-advanced-cropper';

export const CircleStencil = ({ cropper }: StencilProps) => {
	const coordinates = cropper.getStencilCoordinates();
	const transitions = cropper.getTransitions();
	return (
		<div className="circle-stencil"></div>
	)
}
```
## Applying the stencil coordinates

You can compute the style himself, but it's recommended to use `StencilWrapper` component.

```tsx
import React from 'react';
import {
	StencilProps,
	StencilWrapper
} from 'react-advanced-cropper';

export const CircleStencil = ({ cropper }: StencilProps) => {
	const coordinates = cropper.getStencilCoordinates();
	const transitions = cropper.getTransitions();
	return (
		<StencilWrapper className="circle-stencil" transitions={transitions} {...coordinates}>
		</StencilWrapper>
	)
}
```

## Aspect ratio

The stencil should inform the cropper that he has aspect ratio limitation.
You should pass the object with the following type as a ref:
```
{
	aspectRatio: number | {
		minimum?: number;
		maximum?: number;
	}
}
```

Therefore the functional component should be wrapped in `forwardRef` method.
```tsx
import React, { useImperativeHandle, forwardRef } from 'react';
import {
	StencilRef,
	StencilProps,
	StencilWrapper
} from 'react-advanced-cropper';

export const CircleStencil = forwardRef<StencilRef, StencilProps> (({ cropper }: StencilProps, ref) => {
	const coordinates = cropper.getStencilCoordinates();
	const transitions = cropper.getTransitions();

	useImperativeHandle(ref, () => ({
		aspectRatio: 1,
	}));

	return (
		<StencilWrapper className="circle-stencil" transitions={transitions} {...coordinates}>
		</StencilWrapper>
	)
}
```

:::tip
Notice, that in this example `aspectRatio` is equal to `1`. That's because it should be always a circle. It will be converted lately into:
```tsx
{
	minimum: 1,
	maximum: 1
}
```
:::


## Overlay

The preferable way to create the overlay (i.e. black background around the stencil) is the using `StencilOverlay`. There are other
techniques, but they can produce some glitches during the transitions.

```tsx
import React, { useImperativeHandle, forwardRef } from 'react';
import {
	StencilRef,
	StencilProps,
	StencilWrapper,
	StencilOverlay
} from 'react-advanced-cropper';

export const CircleStencil = forwardRef<StencilRef, StencilProps> (({ cropper }: StencilProps, ref) => {
	const coordinates = cropper.getStencilCoordinates();
	const transitions = cropper.getTransitions();

	useImperativeHandle(ref, () => ({
		aspectRatio: 1,
	}));

	return (
		<StencilWrapper className="circle-stencil" transitions={transitions} {...coordinates}>
			<StencilOverlay className="circle-stencil__overlay" />
		</StencilWrapper>
	)
}
```

It's time to add some styling now to make the stencil overlay round and add the little border to it:
```scss
.circle-stencil {
	&__overlay {
		cursor: move;
    border: dashed 2px white;
    box-sizing: border-box;
		border-radius: 50%;
	}
}
```


## Handler

Let's add the handler to resize the stencil:

```tsx
import React, { useImperativeHandle, forwardRef } from 'react';
import {
	StencilRef,
	StencilProps,
	StencilWrapper,
  StencilOverlay
} from 'react-advanced-cropper';

const handlerIcon = require('./handler.svg');

export const CircleStencil = forwardRef<StencilRef, StencilProps> (
	({ cropper }: StencilProps, ref) => {
	const coordinates = cropper.getStencilCoordinates();
	const transitions = cropper.getTransitions();

	useImperativeHandle(ref, () => ({
		aspectRatio: 1,
	}));

	return (
		<StencilWrapper className="circle-stencil" transitions={transitions} {...coordinates}>
			<img src={handlerIcon} className="circle-stencil__handler" />
			<StencilOverlay className="circle-stencil__overlay" />
		</StencilWrapper>
	)
}
```

```scss
.circle-stencil {
	&__handler {
		position: absolute;
		right: 15%;
		top: 14%;
		z-index: 1;
		cursor: ne-resize;
		width: 30px;
		height: 30px;
		display: flex;
		align-items: center;
		justify-content: center;
		transform: translate(50%, -50%);
	}
	&__overlay {
		cursor: move;
    border: dashed 2px white;
    box-sizing: border-box;
		border-radius: 50%;
	}
	&__draggable-area {
		width: 100%;
		height: 100%;
	}
}
```

## Events handling

### Preparing

You may handle drag events himself, but this library provides two very useful components for this goal: `DraggableElement` and `DraggableArea`. The first  one is used for different handlers, lines and etc, the second one is used for dragging the stencil itself.

```tsx
import React, { useImperativeHandle, forwardRef } from 'react';
import {
	StencilRef,
	StencilProps,
	StencilWrapper,
  StencilOverlay
} from 'react-advanced-cropper';

const handlerIcon = require('./handler.svg');

export const CircleStencil = forwardRef<StencilRef, StencilProps>(
	({ cropper }: StencilProps, ref) => {
	const coordinates = cropper.getStencilCoordinates();
	const transitions = cropper.getTransitions();

	useImperativeHandle(ref, () => ({
		aspectRatio: 1,
	}));

	return (
		<StencilWrapper className="circle-stencil" transitions={transitions} {...coordinates}>
			<DraggableElement className="circle-stencil__handler">
    		<img src={handlerIcon} />
    	</DraggableElement>
			<DraggableArea className="circle-stencil__draggable-area">
				<StencilOverlay className="circle-stencil__overlay" />
			</DraggableArea>
		</StencilWrapper>
	)
}
```

Notice, that we still didn't callbacks for the used components. It's time to do it.

### Moving handler

We've' used `DraggableElement` before to wrap the handler. It has `onDrag` handler, that called on handler drag.

This callback has only two arguments:
- `shift: MoveDirections`
	```tsx
	interface MoveDirections {
    left: number;
    top: number;
	}
	```
- `nativeEvent: MouseEvent | TouchEvent`

The first one is the most important. It tells the current shift of the handler, i.e. how much pixels the handler was moved away
from its current position.

We should transform it to resize directions:

![Resize directions](/img/custom-stencil/resize-directions.svg)

The draft of `onResize` method is represented below
```tsx
const onResizeHandler = (shift: MoveDirections) => {
	cropper.resizeCoordinates(
		{
			left: shift.left,
			right: shift.left,
			top: shift.top,
			bottom: shift.top,
		},
		{
			compensate: true,
		},
	);
}
```


:::tip
All values has the same sign. It's because the resize directions tells the cropper
how much each of the stencil edges should be moved. For example, a positive value for `left`
will move left edge to left, a positive value for `right` will move right edge to right.
:::

:::tip
The `compensate` property tells the cropper that if the stencil can't be resized due to intersection of
the cropper boundary, the stencil should try to compensate in another directions.
:::

### Last steps

It's almost ready. We should add only the last callbacks:

- Callback `onDragEnd` for `DraggableElement`
- Callbacks `onMove` and `onMoveEnd` for `DraggableArea`

Fortunately, they are trivial. We just pass the default cropper methods to them.

```tsx
import React, { useImperativeHandle, forwardRef } from 'react';
import {
	StencilRef,
	StencilProps,
	StencilWrapper,
  StencilOverlay,
  DraggableElement,
  DraggableArea,
	MoveDirections,
} from 'react-advanced-cropper';

const handlerIcon = require('./handler.svg');

export const CircleStencil = forwardRef<StencilRef, StencilProps>(
	({ cropper }: StencilProps, ref) => {
	const coordinates = cropper.getStencilCoordinates();
	const transitions = cropper.getTransitions();

	useImperativeHandle(ref, () => ({
		aspectRatio: 1,
	}));

	const onResize = (shift: MoveDirections) => {
		cropper.resizeCoordinates('center', {
			left: shift.left,
			top: shift.left,
		});
	};

	const onMove = (directions: MoveDirections) => {
		cropper.moveCoordinates(directions);
	};

	return (
		<StencilWrapper className="circle-stencil" transitions={transitions} {...coordinates}>
			<DraggableElement
				className="circle-stencil__handler"
				onMove={onResize}
				onMoveEnd={cropper.resizeCoordinatesEnd}
			>
    		<img src={handlerIcon} />
    	</DraggableElement>
			<DraggableArea
				className="circle-stencil__draggable-area"
				onMove={onMove}
				onMoveEnd={cropper.moveCoordinatesEnd}
			>
				<StencilOverlay className="circle-stencil__overlay" />
			</DraggableArea>
		</StencilWrapper>
	)
}
```

## Result

The full ready-to-use source code of this example is [here](https://codesandbox.io/s/react-circle-cropper-v3l5z).

<CustomStencil />
